package org.infodavid.common.impl.services;

import java.util.List;

import javax.persistence.PersistenceException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.infodavid.common.annotations.AImplementation;
import org.infodavid.common.annotations.AImplementation.ERuntime;
import org.infodavid.common.dto.IDocumentCategoryListItem;
import org.infodavid.common.dto.IDocumentVersionListItem;
import org.infodavid.common.impl.services.helpers.ServiceHelper;
import org.infodavid.common.model.ICategory;
import org.infodavid.common.model.IDocument;
import org.infodavid.common.model.IDocumentCategory;
import org.infodavid.common.model.IDocumentVersion;
import org.infodavid.common.model.behaviors.IApprovable.EApprovalStatus;
import org.infodavid.common.model.criteria.ICategoryCriteria;
import org.infodavid.common.model.criteria.IDocumentVersionCriteria;
import org.infodavid.common.persistence.DocumentCategoryDataService;
import org.infodavid.common.persistence.DocumentDataService;
import org.infodavid.common.persistence.DocumentVersionDataService;
import org.infodavid.common.services.DocumentService;
import org.infodavid.common.services.IApplicationContext;
import org.infodavid.common.services.exception.IllegalOperationException;
import org.infodavid.common.services.exceptions.ServiceException;

/**
 * The Class DocumentServiceImpl.
 */
@AImplementation(value = DocumentService.class, runtime = ERuntime.DEFAULT)
public class DocumentServiceImpl extends DocumentService {

  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(DocumentServiceImpl.class);

  /** The helper. */
  private final ServiceHelper<IDocumentVersionListItem,IDocumentVersion,Long,IDocumentVersionCriteria> helper;

  /** The helper. */
  private final ServiceHelper<IDocumentCategoryListItem,IDocumentCategory,Long,ICategoryCriteria> categoryHelper;

  /**
   * The Constructor.
   */
  protected DocumentServiceImpl() {
    super();

    helper =
        new ServiceHelper<IDocumentVersionListItem,IDocumentVersion,Long,IDocumentVersionCriteria>(
            DocumentVersionDataService.getInstance());
    categoryHelper =
        new ServiceHelper<IDocumentCategoryListItem,IDocumentCategory,Long,ICategoryCriteria>(
            DocumentCategoryDataService.getInstance());
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#add(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.IDataObject)
   */
  @Override
  public long add(final IApplicationContext context, final IDocumentVersion value)
      throws PersistenceException, ServiceException {
    helper.validate(context, getValidator(), value);

    final IInterceptorEvent evt = preExecute(context, ADD_ACTION, value);
    // TODO versioning mgt
    final long key = helper.add(context, value).longValue();

    postExecute(evt.setResults(value));

    LOGGER.info("A new document has been added (" + key + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("A new document has been added: " + value + " (" + key + ')');
    }

    return key;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.services.DocumentService#findLastVersionByName(org.infodavid.common.services
   * .IApplicationContext, java.lang.String)
   */
  @Override
  public IDocumentVersion findLastVersionByName(final IApplicationContext context,
      final String value) throws PersistenceException, ServiceException {
    if (StringUtils.isEmpty(value)) {
      throw new IllegalArgumentException(IDocumentVersion.NAME);
    }

    ServiceUtil.validate(context);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, value);
    final IDocumentVersion result =
        ((DocumentVersionDataService)helper.getDataService()).findLastVersionByName(
            context.getPersistenceSession(), value);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.DocumentService#findByLastVersion(org.infodavid.common.services.
   * IApplicationContext, java.lang.Long)
   */
  @Override
  public IDocumentVersion findByLastVersion(final IApplicationContext context, final Long key)
      throws PersistenceException, ServiceException {
    ServiceUtil.validate(key);
    ServiceUtil.validate(context);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, key);
    final IDocumentVersion result =
        ((DocumentVersionDataService)helper.getDataService()).findLastVersion(
            context.getPersistenceSession(), key);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.services.DocumentService#findByLastVersionByApproval(org.infodavid.common.
   * services.IApplicationContext, java.lang.Long,
   * org.infodavid.common.model.IApprovable.EApprovalStatus)
   */
  @Override
  public IDocumentVersion findByLastVersionByApproval(final IApplicationContext context,
      final Long key, final EApprovalStatus status) throws PersistenceException, ServiceException {
    ServiceUtil.validate(key);
    ServiceUtil.validate(context);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, key, status);
    final IDocumentVersion result =
        ((DocumentVersionDataService)helper.getDataService()).findLastVersionByApproval(
            context.getPersistenceSession(), key, status);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#get(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @SuppressWarnings("boxing")
  @Override
  public IDocumentVersion get(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    helper.validate(context, key);

    final IInterceptorEvent evt = preExecute(context, GET_ACTION, key);
    final IDocumentVersion result = helper.get(context, Long.valueOf(key));

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.DocumentService#getDocument(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @Override
  @SuppressWarnings("boxing")
  public IDocument getDocument(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    helper.validate(context, key);

    final IInterceptorEvent evt = preExecute(context, GET_ACTION, key);
    final IDocument result =
        DocumentDataService.getInstance().findByPrimaryKey(context.getPersistenceSession(), key);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.DocumentService#findDocumentByName(org.infodavid.common.services.
   * IApplicationContext, java.lang.String)
   */
  @Override
  public IDocument findDocumentByName(final IApplicationContext context, final String value)
      throws PersistenceException, ServiceException {
    if (StringUtils.isEmpty(value)) {
      throw new IllegalArgumentException(IDocument.NAME);
    }

    ServiceUtil.validate(context);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, value);
    final IDocument result =
        DocumentDataService.getInstance().findByName(context.getPersistenceSession(), value);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#getEntities(org.infodavid.common.services.
   * IApplicationContext, java.util.List, org.infodavid.common.services.ISearchCriteria)
   */
  @Override
  public long findEntities(final IApplicationContext context,
      final List<IDocumentVersionListItem> entities, final IDocumentVersionCriteria criteria)
      throws PersistenceException, ServiceException {
    ServiceUtil.validate(context);
    helper.validate(entities);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, criteria);
    final long result =
        ((DocumentVersionDataService)helper.getDataService()).find(context.getPersistenceSession(),
            entities, criteria);

    postExecute(evt.setResults(entities));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#remove(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @Override
  public void remove(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    throw new IllegalOperationException("A document version can not be removed (" + key + ')');
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#update(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.IDataObject)
   */
  @Override
  public void update(final IApplicationContext context, final IDocumentVersion value)
      throws PersistenceException, ServiceException {
    add(context, value); // a version is always added and never updated using this service (see data
                         // service)
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#add(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.IDataObject)
   */
  @Override
  public long addCategory(final IApplicationContext context, final IDocumentCategory value)
      throws PersistenceException, ServiceException {
    categoryHelper.validate(context, getCategoryValidator(), value);

    final IInterceptorEvent evt = preExecute(context, ADD_ACTION, value);
    final long key = categoryHelper.add(context, value).longValue();

    postExecute(evt.setResults(value));

    LOGGER.info("A new category has been added (" + key + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("A new category has been added: " + value + " (" + key + ')');
    }

    return key;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#getEntities(org.infodavid.common.services.
   * IApplicationContext, java.util.List, org.infodavid.common.services.ISearchCriteria)
   */
  @Override
  public long findCategories(final IApplicationContext context,
      final List<IDocumentCategoryListItem> entities, final ICategoryCriteria criteria)
      throws PersistenceException, ServiceException {
    ServiceUtil.validate(context);
    categoryHelper.validate(entities);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, criteria);
    final long result =
        ((DocumentCategoryDataService)categoryHelper.getDataService()).find(
            context.getPersistenceSession(), entities, criteria);

    postExecute(evt.setResults(entities));

    return result;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.services.DocumentCategoryService#findByName(org.infodavid.common.services.
   * IApplicationContext, java.lang.String)
   */
  @Override
  public IDocumentCategory findCategoryByName(final IApplicationContext context, final String value)
      throws PersistenceException, ServiceException {
    if (StringUtils.isEmpty(value)) {
      throw new IllegalArgumentException(IDocumentCategory.NAME);
    }

    ServiceUtil.validate(context);

    final IInterceptorEvent evt = preExecute(context, FIND_ACTION, value);
    final IDocumentCategory result =
        ((DocumentCategoryDataService)categoryHelper.getDataService()).findByName(
            context.getPersistenceSession(), value);

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#get(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @SuppressWarnings("boxing")
  @Override
  public IDocumentCategory getCategory(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    categoryHelper.validate(context, key);

    final IInterceptorEvent evt = preExecute(context, GET_ACTION, key);
    final IDocumentCategory result = categoryHelper.get(context, Long.valueOf(key));

    postExecute(evt.setResults(result));

    return result;
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see org.infodavid.common.services.AbstractEntityService#remove(org.infodavid.common.services.
   * IApplicationContext, long)
   */
  @SuppressWarnings({
      "boxing", "rawtypes", "unchecked"
  })
  @Override
  public void removeCategory(final IApplicationContext context, final long key)
      throws PersistenceException, ServiceException {
    categoryHelper.validate(context, key);

    final IInterceptorEvent evt = preExecute(context, REMOVE_ACTION, key);
    final IDocumentCategory existing = categoryHelper.get(context, Long.valueOf(key));
    final ICategory parent = existing == null ? null : existing.getParent();

    if (parent != null) {
      parent.removeSubCategory(existing);
    }

    categoryHelper.remove(context, existing);
    postExecute(evt.setResults(existing));

    LOGGER.info("A category has been removed (" + key + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("A category has been removed: " + existing + " (" + key + ')');
    }
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.services.DocumentService#updateCategory(org.infodavid.common.services.
   * IApplicationContext, org.infodavid.common.model.IDocumentCategory)
   */
  @Override
  public void updateCategory(final IApplicationContext context, final IDocumentCategory value)
      throws PersistenceException, ServiceException {
    categoryHelper.validate(context, getCategoryValidator(), value);

    final IInterceptorEvent evt = preExecute(context, UPDATE_ACTION, value);

    categoryHelper.update(context, value);
    postExecute(evt.setResults(value));

    LOGGER.info("A category has been updated (" + value.getKey() + ')');

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("A category has been updated: " + value + " (" + value.getKey() + ')');
    }
  }
}
